iT邦幫忙

2022 iThome 鐵人賽

DAY 4
1
Modern Web

終究都要學 React 何不現在學呢?系列 第 4

終究都要學 React 何不現在學呢? - React 基礎 - JSX - (4)

  • 分享至 

  • xImage
  •  

前言

常常聽到人家說 JSX 這是什麼東西?好像對於寫 React 的人來講是一個必須要學的東西?

JSX 介紹

撰寫 React 之前你一定會需要了解 JSX,而前面我們也都有一直寫到 JSX,只是沒有特別了解它而已,所以這邊就要來認識一下 JSX 是什麼。

https://ithelp.ithome.com.tw/upload/images/20220918/20119486ahYeoZL6Nt.jpg

如同剛剛前面所說的,我們已經有撰寫過 JSX,或許只是你沒發現而已,那...前面我們什麼時候寫過 JSX 呢?讓我們拉前面範例一下

const app = document.querySelector('#app');
const root = ReactDOM.createRoot(app);

root.render(<h1>Hello React</h1>);

CodePen 連結

其實 root.render(<h1>Hello React</h1>); 裡面這一段 <h1>Hello React</h1> 就是 JSX。

JSX 它既不是 HTML 也不是一個字串,更不是 CSS (廢話),是 React 針對 JavaScript 所出的擴充語法,全名是 JavaScript & XML,是一個 HTML in JavaScript 的擴充語法(以下簡稱 JSX)。

那 JSX 基礎語法是如何呢?它就是如下方範例這樣撰寫

const example = <h1>Hello React</h1>;

對於第一次看到 JSX 語法的人來講可能會覺得很 Magic!為什麼會覺得是一個魔術呢?畢竟我們在 JavaScript 中撰寫 HTML 是必須使用單或雙引號、反引號來包覆,可是 JSX 卻不用。

而當你試著輸出 const example = <h1>Hello React</h1>; 這一段變數時,你會發現 JSX 會自動轉換為一個 React Element

https://ithelp.ithome.com.tw/upload/images/20220918/20119486CmOeZmsnrj.png

CodePen 連結

這代表著什麼意思呢?在第二篇「終究都要學 React 何不現在學呢? - React 基礎 - Hello React - (2)」中我們有提到 React.createElement 的寫法:

const app = document.querySelector('#app');
const root = ReactDOM.createRoot(app);

const element = React.createElement('h1', {
  children: 'Hello React',
})

root.render(element);

CodePen 連結

我相信你在實際開發時,你應該不會想要每次新增一個元素就寫一次 React.createElement

const app = document.querySelector('#app');
const root = ReactDOM.createRoot(app);

const content = React.createElement('p', {
  children: 'lorem lorem lorem lorem lorem lorem',
})

const element = React.createElement('h1', {
  children: ['Hello React', content],
})

root.render(element);

CodePen 連結

相較之下直接使用 JSX 會更香更好學,彷彿就跟寫原生 HTML 沒有什麼兩樣

const app = document.querySelector('#app');
const root = ReactDOM.createRoot(app);
const example = <h1>
  Hello React
  <p>lorem lorem lorem lorem lorem lorem</p>
</h1>;
root.render(example);

CodePen 連結

JSX 鍵 (Key)

接著讓我們拉一下前面其中一個範例,剛剛有少講到一個東西

const app = document.querySelector('#app');
const root = ReactDOM.createRoot(app);

const content = React.createElement('p', {
  children: 'lorem lorem lorem lorem lorem lorem',
})

const element = React.createElement('h1', {
  children: ['Hello React', content],
})

root.render(element);

CodePen 連結

如果你是使用 React.createElement 的方式的話,基本上你會發現這個 Console 會噴一段訊息給你

"Warning: Each child in a list should have a unique 'key' prop. See https://reactjs.org/link/warning-keys for more information.%s" "
h1"

當然這個錯誤訊息並不局限於 React.createElement,如果你將 JSX 改寫成以下跑迴圈,其實也會發生該問題

const app = document.querySelector('#app');
const root = ReactDOM.createRoot(app);

const arr = [''];
const P = () => (<p>lorem lorem lorem lorem lorem lorem</p>);

const example = <h1>
  Hello React
  { arr.map(() => (<P />)) }
</h1>;
root.render(example);

CodePen 連結

解決方式非常簡單,只需要加上 key 就可以解決了,以 React.createElement 來講就是以下改法

const app = document.querySelector('#app');
const root = ReactDOM.createRoot(app);

const content = React.createElement('p', {
  children: 'lorem lorem lorem lorem lorem lorem',
  key: 'content',
})

const element = React.createElement('h1', {
  children: ['Hello React', content],
})

root.render(element);

CodePen 連結

而 JSX 寫法則是在元件上面補一個 key 屬性,並搭配 index 建立一個唯一值就可以了

const app = document.querySelector('#app');
const root = ReactDOM.createRoot(app);

const arr = [''];
const P = () => (<p>lorem lorem lorem lorem lorem lorem</p>);

const example = <h1>
  Hello React
  { arr.map((item, index) => (<P key={ `p${index}` }/>)) }
</h1>;
root.render(example);

CodePen 連結

那麼你可能會好奇了,為什麼會需要 keykey 有什麼用?key 其實主要是幫助 React 識別這個元件是否被調整或刪除等行為,來決定是否重新渲染,以範例來講我們可以看一下下方範例

function App (){
  const [ arrayData, setArrayData ] = React.useState([
    {
      id: '',
      name: 'Ray',
      price: 80,
    },
    {
      name: 'John',
      price: 90,
    },
    {
      name: 'Mary',
      price: 100,
    }
  ]);

  function sayHi () {
    setArrayData([ ...arrayData.reverse() ])
  }

  return (
    <div>
      <ul>
        { arrayData.map((item) => (
          <li>{ item.name } - <input type="text"/></li>
        )) }
      </ul>
      <button type="button" onClick={ sayHi }>反轉</button>
    </div>
  )
}

const app = document.querySelector('#app');
const root = ReactDOM.createRoot(app);
root.render(<App />);

CodePen 連結

上方是一個簡單範例,你可以先針對 input 欄位輸入 1、2、3,接著按下「反轉」按鈕,你會發現欄位還是會保持原本,但是資料已經反轉了,而這其實就會導致一些問題發生。

接著你試著補上 key 再輸入一次 1、2、3,接著按下「反轉」按鈕,這時候你應該會發現欄位的 1 跟 3 會消失,因為 React 檢測到這兩個資料有變化,因此決定重新渲染

function App (){
  const [ arrayData, setArrayData ] = React.useState([
    {
      id: '',
      name: 'Ray',
      price: 80,
    },
    {
      name: 'John',
      price: 90,
    },
    {
      name: 'Mary',
      price: 100,
    }
  ]);

  function reverseData () {
    setArrayData([ ...arrayData.reverse() ])
  }

  return (
    <div>
      <ul>
        { arrayData.map((item, index) => (
          <li key={ `${item.name}${index} `}>{ item.name } - <input type="text"/></li>
        )) }
      </ul>
      <button type="button" onClick={ reverseData }>反轉</button>
    </div>
  )
}

CodePen 連結

這就是為什麼 React 會建議你補上 key 的原因,最後請記住一件事情,如果可以的話,請使用陣列資料中的唯一值當作 key 下下策才使用索引,否則很有可能發生一些奇怪的事情。

JSX 表達式 (Expression)

接著我們繼續聊 JSX 的部分,JSX 其實也可以嵌入一個表達式 (Expression)

如果不知道什麼是表達式,可閱讀我之前文章:JavaScript 核心觀念(11)-運算子、型別與文法-陳述式與表達式

使用方式非常簡單只需要在 JSX 中使用大括號、花括號(以下統一稱花括號)並填寫變數即可,只要它是一個表達式,那麼就可以正常運作

const app = document.querySelector('#app');
const root = ReactDOM.createRoot(app);

const myName = 'Ray';
const example = <h1> Hello { myName } </h1>;

root.render(example);

CodePen 連結

由於可以支援表達式的關係,因此我們也可以寫一些函式來回傳結果

const sayHi = (status) => {
  if(status) {
    return 'Ray';
  }
  return 'Tom';
}

const example = <h1> Hello { sayHi(false) } </h1>;

root.render(example);

CodePen 連結

甚至寫一些奇怪的 Code,只要你是表達式通通都可以

const app = document.querySelector('#app');
const root = ReactDOM.createRoot(app);

const example = <h1> Hello { console.log('Hello') } </h1>;

root.render(example);

CodePen 連結

(附註:console.log 本身也是屬於表達式一種,只是預設不會回傳任何東西。)

那麼因為 JSX 只接受表達式的關係,所以是無法在 JSX 中撰寫 if...else 判斷式的

const app = document.querySelector('#app');
const root = ReactDOM.createRoot(app);

const example = <h1> Hello { if(true) { console.log('Ray') } } </h1>; // Error

root.render(example);

CodePen 連結

但是我們是可以在 JSX 中撰寫三元運算子的

const app = document.querySelector('#app');
const root = ReactDOM.createRoot(app);

const example = <h1> Hello { true ? 'Ray' : 'Tom' } </h1>;

root.render(example);

CodePen 連結

那麼 JSX 這個特性與我們在撰寫 Vue SFC(單一元件檔, Single File Component)的 template 區塊也是一樣相同的。

JSX 陣列展開 (Spread)

另外 JSX 也有一些特別的功能,也就是可以自動展開陣列內容

const app = document.querySelector('#app');
const root = ReactDOM.createRoot(app);

const list = [
  <p key="e1">example 1</p>,
  <p key="e2">example 2</p>,
]

const example = <h1> Hello { list } </h1>;

root.render(example);

CodePen 連結

JSX 註解(Comment)

JSX 中也是可以撰寫註解的,只是你要注意註解必須寫在花括號中,並且註解不要寫在同一行,如下

const example = <h1> Hello { // 這是一段註解 } </h1>;

CodePen 連結

上方這種寫法將會導致後方都被註解而出現錯誤,正確應如下方寫法

const example = <h1> Hello { /* 這是一段註解 */ }</h1>;

CodePen 連結

因此 JSX 中支援的註解是有星號的註解唷。

JSX 樣式(Style 與 Class)

實際開發時我們必定會替畫面給予一些樣式,如 HTML Style 跟 HTML Class,但這兩者在 JSX 中都有一些細節要注意,舉例來講 HTML Style 就要注意 JSX 採用的是 camelCase 撰寫方式(意旨遇到 - 減字號就轉大寫)

const app = document.querySelector('#app');
const root = ReactDOM.createRoot(app);
const example = <h1 style={ {backgroundColor: 'black', color: 'white'} }>Hello React</h1>;
root.render(example);

CodePen 連結

這邊要注意 style 必定會是傳入一個花括號,裡面再傳入一個物件,為了方便辨別,所以上方寫法我改成以下會比較好觀看

const app = document.querySelector('#app');
const root = ReactDOM.createRoot(app);

const customStyle = {
  backgroundColor: 'black',
  color: 'white'
};

const example = <h1 style={ customStyle }>Hello React</h1>;
root.render(example);

CodePen 連結

只是這邊要特別注意如果你傳入的是調整字型大小或是 margin 這類 CSS 語法並且剛剛好是單純的數字的話 JSX 預設單位會是 px

const app = document.querySelector('#app');
const root = ReactDOM.createRoot(app);

const customStyle = {
  backgroundColor: 'black',
  color: 'white',
  marginTop: 20,
  fontSize: 72,
};

const example = <h1 style={ customStyle }>Hello React</h1>;
root.render(example);

CodePen 連結

這一段整體來講也與 Vue 部分神似(以下出自 Vue 官方文件並微調)

<div id="app">
  <h1 :style="styleObject">Hello Vue</h1>
</div>
const { createApp } = Vue;

createApp({
  data() {
    return {
      styleObject: {
        backgroundColor: 'black',
        color: 'white'
      }
    }
  },
}).mount('#app')

CodePen 連結

那麼由於 JSX 本身是 JavaScript 擴充語法,因此裡面有一些地方就不會是原本的 HTML 寫法,如 class 變成 className

.box {
  color: yellow
}
const app = document.querySelector('#app');
const root = ReactDOM.createRoot(app);

const example = <h1 className="box">Hello React</h1>;
root.render(example);

CodePen 連結

那為什麼 class 會變成 className 呢?主要原因是因為 class 是 JavaScript 保留字,因此才會調整成 className

後記

本文將會同步更新到我的部落格


上一篇
終究都要學 React 何不現在學呢? - React 基礎 - Class?Hooks? - (3)
下一篇
終究都要學 React 何不現在學呢? - React 基礎 - 事件處理 - (5)
系列文
終究都要學 React 何不現在學呢?30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言